classOrObject.variableName
classOrObject.variableName := value
In contrast with many other object-oriented programming languages, these forms for querying and changing instance variables do not read or write the variable directly. Instead, they call special generic functions called setters (to change the variable) and getters (to query a variable's value), which then operate on the class or object to retrieve or change the value of that variable.
The use of setters and getters to access a class or object's variables allows a great deal of flexibility in querying or changing that variable.
class variables
or instance
variables
sections of the class
or object
definitions, as described earlier in this chapter on page 113 and again on page 119.class variableName ( classList )In addition to these simple forms where you specify either the name of the variable or the name and its initial value, each variable definition can also specify a set of optional keywords to specify, primarily, how this variable is to be handled when this class or object is stored in the ScriptX object store. Those keywords appear in a specific order before the variable name and its initial value:
class variables
varName
varName:initialValue
. . .
instance variables
varName
varName:initialValue
. . .
end
[ readOnly ] [ transient [ initializer function ] ] \The qualifiers to each variable are all optional, but they must be specified in the order shown for them to be processed correctly.
[ reference ] varName [ :initialValue ]
The first optional qualifier, readOnly
, indicates that this variable can be retrieved but cannot be changed. When this variable is created, only a getter method is constructed for it.
The remaining qualifiers are used for classes and objects that are stored into ScriptX storage containers, part of the ScriptX object store. By default, when a class or object is stored, all of its class and instance variables are also stored with their current values. Additionally, when the class or object is loaded from the storage container, all the objects that its instance variables hold are also retrieved.
The remaining three optional qualifiers allow you to specialize how each variable is to behave when stored:
The transient
qualifier specifies that when the class or object is stored, the value of this variable is discarded. Transient variables have initial values of undefined
when they are restored, unless an initializer function is specified.
The initializer
qualifier specifies that when a transient object is restored, the function specified by function is executed to set the initial value of this instance variable. (Only transient variables can take an initializer.) The function expression can hold any of the following expressions, where appropriate:
myArray[1]
)
self.x
)
reference
qualifier specifies that when the class or object this variable belongs to is restored, the object this variable holds is not restored along with it. Instead, that object is only restored when the variable is queried. The reference
qualifier is useful for variables that hold very large objects that do not immediately need to be loaded into memory when your ScriptX program starts running.For further details on the ScriptX object store and how objects are stored, see the ScriptX Components Guide.
Virtual class or instance variables are particularly useful for variables that may frequently change as a consequence of some other operation on that object. Take, for example, an object that holds other objects such as a collection. That collection object might have a size
instance variable that returns the number of elements in that collection. Any operation that adds or removes elements from that array would have to change the value of that instance variable, requiring an extra step (and more time) for each add or removal operation.
If size
is a virtual instance variable, the size would never be stored anywhere in memory. Instead, when the size
instance variable is queried, the getter method counts the items in the array and returns that value. No memory is used for that number, and each method that adds or removes elements from the array has one less operation it has to keep track of.
When you define a real class instance variable, ScriptX automatically creates a setter and getter method for that variable when the class or object is created. To define virtual instance variables, you must define setter and getter methods for those variables yourself.
ScriptX has two forms for defining setter and getter methods (two for creating setter methods, and two for creating getter methods):
method variableSetter self value -> bodyIn each definition, variable is the name of the class or instance variable. In the first two definitions, the variable name appears just before the words
method variableGetter self -> body
method set variable self value -> body
method get variable self -> body
Setter
or Getter
(with no space in between). For example, the getter method for the virtual instance variable x
would be the xGetter
method. In the second two definitions, the variable name appears after the reserved words get
or set
.
Each of the setter and getter forms are equivalent; that is, the method set
form is equivalent to the variableSetter
form and the method get
form is equivalent to variableGetter
.
Getter methods always have only one argument, self
, and setters have two, self
and the value
the variable is to be "set" to. The body part of each method should always return the value of the virtual instance variable as its last argument.
If the virtual instance variable is to be considered read-only, define only a getter method for that variable and not a setter method.
The names of virtual instance variables should not be defined in a class variables
or instance variables
definition; remember, as virtual instance variables, simply the existence of the setter and getter methods gives those variables the appearance of reality.
The following examples show how to define setter and getter methods within a class or object definition. The first example is trivial. It simply says that the instance variable ten
has a value of 10
.
method tenGetter self -> return 10
method tenSetter self value -> (
print "Cannot change ten. ten is 10."
10
)
The second example creates a virtual instance variable called position
, which could be used to query or change the object's position in some two-dimensional space. Here position
is built from the existing x
and y
instance variables. Setting position
, an array of two elements, is accomplished by setting the values of x
and y
.
method get position self -> (
return #(self.x, self.y)
)
method set position self value -> (
self.x := value[1]
self.y := value[2]
return value
)
The third example defines a virtual instance variable lineWidth
that provides direct access to an instance variable that is defined by a member object.
method lineWidthGetter self -> (
return self.stroke.lineWidth
)
The lineWidthGetter
method could be defined as a free method for subclasses of certain core classes, such as TextPresenter
. (Note that you cannot change a core method in a core class, but you can change a method in a subclass of a core class.)
-- create a subclass of TextPresenter and defind lineWidthGetter as a -- free method on itclass MyTextPresenter (TextPresenter) end
method lineWidthGetter self {class MyTextPresenter} -> (
return self.stroke.lineWidth
)
-- now create a test of lineWidthGetter
global myTP := new MyTextPresenter \
stroke:blackBrush fill:whiteBrush \
boundary:(new Rect x2:200 y2:200) target:"inconceivable"
myTP.lineWidth
1
class
or object
expressions. You can, however, augment or change the behavior of those setters and getters for real variables, just as you did for virtual variables (for example, to print debugging messages or to change the value of some other part of the class or object).You can either specialize the setter and getter for a variable that has been defined in this same class or object, or you can specialize the setter and getter for a variable that has been defined in a superclass and inherited by this class or object. To specialize setters and getters, you use the same method definition syntax that you used to define virtual instance variables, as described on page 143.
nextMethod
to pass the method call up the class hierarchy to the appropriate implementation of that method.
For example, suppose you have defined a class called VerbosePresenter
that inherits from the class TwoDPresenter
. The TwoDPresenter
class defines x
and y
instance variables to indicate the position of your presenter in a coordinate space. In VerbosePresenter
, you want to augment the behavior of the setter methods for x
and y
to print a message to the debugging stream each time x
or y
is changed. You also want to accept floating point values and store them for precision, but round them to the nearest integer value for actual display.
To do this, store the values with higher precision in another set of instance variables which you define. Define setter methods for x
and y
to transform the value from floating point to integer, print a message, and then call nextMethod
, which allows the superclass to actually set the variable:
class VerbosePresenter (TwoDPresenter)
instance variables
_x, _y
instance methods
method xSetter self value -> (
self._x := value
local val
format debug "x is stored internally as %* " value @normal
format debug "but displayed as %*\n" \
(val := round value) @normal
nextMethod self val
)
method ySetter self value -> (
self._y := value
local val
format debug "y is stored internally as %* " value @normal
format debug "but displayed as %*\n" \
(val := round value) @normal
nextMethod self val
)
end
As previously noted, you should avoid setting any instance variables which are owned by superclasses before calling nextMethod
. In the example above, the instance variables which are set (_x
and _y
) are not owned by any superclasses because they are defined in the new class VerbosePresenter
. Therefore, in this particular case, there is no problem with calling nextMethod
last.
If you do not specify nextMethod
as the last expression in the setter or getter method body, you need to explicitly return the value of the variable as the last expression.
method xGetter self {class VerbosePresenter} -> (
local value := nextMethod self
format debug "x is displayed as %* " value @normal
format debug "but stored internally as %*\n" self._x @normal
return value
)
method yGetter self {class VerbosePresenter} -> (
local value := nextMethod self
format debug "y is displayed as %* " value @normal
format debug "but stored internally as %*\n" self._y @normal
return value
)
class
or object
expression, provide a way of bypassing the normal setter and getter mechanism and directly gaining access to the memory location that stores the value of that variable. When you override the default setter or getter behavior for a variable, you lose the ability to directly access the variable. For example, to change the default behavior of a getter method for the variable x
, you would have to use the expression self.x
in the body of that getter method, which calls the getter method for x
that you just defined, which queries self.x
, and so on.You can mimic the effect of augmenting the default setter and getter behavior by implementing your variable as a virtual variable, and then using a "placeholder" variable to hold the actual value of the variable whose behavior you are augmenting.
For example, suppose your class Person
has an instance variable called name
which can only have values of the class String
or of one of its subclasses. Because of this restriction on the possible values of name
, you need to define a nameSetter
method that checks the class of the value to which the variable is set to make sure that it is a string, and reports an error otherwise.
Because you cannot change the default behavior of a real variable defined on this class, you create a placeholder for that real variable, for example, a real variable called _name
. Then implement your nameSetter
method, and in the body of the method definition, use the placeholder variable _name
instead of the variable name
to hold the actual name value:
class Person ()
instance variables _name
instance methods
method nameSetter self value -> (
if ((isaKindOf value String) == false) then
print "bad value for IV name."
else self._name := value
)
end
Now, if you instantiate the class Person
, you can use name
just as if it were a real variable, and nameSetter
checks to make sure the object you are assigning to name
is of the right class.
foo := new Person
foo.name := 43
"bad value for IV name"
foo.name := "Elsa"
"Elsa"
This next example uses a fake placeholder variable (_radius
) for the virtual variable radius
in the class MyRoundClass
:
class MyRoundClass ()
instance variables _radius
instance methods
method set radius self value -> (
format debug "changing radius to %*" value @normal
self._radius := value
)
method get radius self -> (
format debug "The value of radius is %*" self._radius @normal
return self._radius
)
end
This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.